Removing an old, cherished, yet pointless caveat "This documentation is
[supercollider.git] / Help / Streams-Patterns-Events / A Practical Guide / PG_Cookbook03_External_Control.html
blobbebdcff7fcadd735866805d9afa3062e34de4d8e
1 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
2 <html>
3 <head>
4 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
5 <meta http-equiv="Content-Style-Type" content="text/css">
6 <title></title>
7 <meta name="Generator" content="Cocoa HTML Writer">
8 <meta name="CocoaVersion" content="949.43">
9 <style type="text/css">
10 p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 18.0px Helvetica}
11 p.p2 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px}
12 p.p3 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Helvetica}
13 p.p4 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica}
14 p.p5 {margin: 0.0px 0.0px 0.0px 0.0px; font: 9.0px Monaco}
15 p.p6 {margin: 0.0px 0.0px 0.0px 0.0px; font: 9.0px Monaco; color: #0016bd}
16 p.p7 {margin: 0.0px 0.0px 0.0px 0.0px; font: 9.0px Monaco; color: #ae1a19}
17 p.p8 {margin: 0.0px 0.0px 0.0px 0.0px; font: 9.0px Monaco; min-height: 12.0px}
18 p.p9 {margin: 0.0px 0.0px 0.0px 0.0px; font: 9.0px Monaco; color: #606060}
19 p.p10 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; color: #001bec}
20 span.s1 {color: #0016bd}
21 span.s2 {color: #000000}
22 span.s3 {color: #2b7000}
23 span.s4 {color: #606060}
24 span.s5 {text-decoration: underline ; color: #001bec}
25 span.s6 {font: 12.0px Helvetica}
26 span.Apple-tab-span {white-space:pre}
27 </style>
28 </head>
29 <body>
30 <p class="p1"><b>Pattern control by external device</b></p>
31 <p class="p2"><br></p>
32 <p class="p3"><b>Control of parameters by MIDI or HID</b></p>
33 <p class="p2"><br></p>
34 <p class="p4">To get pattern values from a HID device, use the <a href="../Patterns/PhidKey.html"><span class="s1">PhidKey</span></a> or <a href="../Patterns/PhidSlot.html"><span class="s1">PhidSlot</span></a> pattern.</p>
35 <p class="p2"><br></p>
36 <p class="p4">For MIDI, the best approach is to save an incoming value into a variable, and then use <a href="../Patterns/Pfunc.html"><span class="s1">Pfunc</span></a> to access the variable for each event.</p>
37 <p class="p2"><br></p>
38 <p class="p5">(</p>
39 <p class="p5">~legato = 1;</p>
40 <p class="p6"><span class="s2">c = </span>CCResponder<span class="s2">({ </span>|src, chan, num, value|</p>
41 <p class="p5"><span class="Apple-tab-span"> </span>~legato = value.linlin(0, 127, 0.1, 2.5)</p>
42 <p class="p7"><span class="s2">}, num: 1);<span class="Apple-tab-span"> </span></span>// num: 1 means modwheel</p>
43 <p class="p5">)</p>
44 <p class="p8"><br></p>
45 <p class="p5">(</p>
46 <p class="p5">p = <span class="s1">Pbind</span>(</p>
47 <p class="p5"><span class="Apple-tab-span"> </span><span class="s3">\degree</span>, <span class="s1">Pwhite</span>(-7, 12, <span class="s1">inf</span>),</p>
48 <p class="p5"><span class="Apple-tab-span"> </span><span class="s3">\dur</span>, <span class="s1">Pwrand</span>([0.25, <span class="s1">Pn</span>(0.125, 2)], #[0.8, 0.2], <span class="s1">inf</span>),</p>
49 <p class="p7"><span class="s2"><span class="Apple-tab-span"> </span></span><span class="s3">\legato</span><span class="s2">, </span><span class="s1">Pfunc</span><span class="s2"> { ~legato }<span class="Apple-tab-span"> </span></span>// retrieves value set by MIDI control</p>
50 <p class="p5">).play;</p>
51 <p class="p5">)</p>
52 <p class="p8"><br></p>
53 <p class="p5">p.stop;</p>
54 <p class="p5">c.remove;</p>
55 <p class="p2"><br></p>
56 <p class="p4">If Pfunc {<span class="Apple-converted-space">  </span>} is bothersome in the Pbind, a <a href="../../Libraries/JITLib/Patterns/PatternProxy.html"><span class="s1">PatternProxy</span></a> or <a href="../../Libraries/JITLib/Patterns/Pdefn.html"><span class="s1">Pdefn</span></a> could also serve the purpose.</p>
57 <p class="p2"><br></p>
58 <p class="p5">(</p>
59 <p class="p5">~legato = <span class="s1">PatternProxy</span>(1);</p>
60 <p class="p6"><span class="s2">c = </span>CCResponder<span class="s2">({ </span>|src, chan, num, value|</p>
61 <p class="p5"><span class="Apple-tab-span"> </span>~legato.source = value.linlin(0, 127, 0.1, 2.5)</p>
62 <p class="p5">}, num: 1);</p>
63 <p class="p5">)</p>
64 <p class="p8"><br></p>
65 <p class="p5">(</p>
66 <p class="p5">p = <span class="s1">Pbind</span>(</p>
67 <p class="p5"><span class="Apple-tab-span"> </span><span class="s3">\degree</span>, <span class="s1">Pwhite</span>(-7, 12, <span class="s1">inf</span>),</p>
68 <p class="p5"><span class="Apple-tab-span"> </span><span class="s3">\dur</span>, <span class="s1">Pwrand</span>([0.25, <span class="s1">Pn</span>(0.125, 2)], #[0.8, 0.2], <span class="s1">inf</span>),</p>
69 <p class="p5"><span class="Apple-tab-span"> </span><span class="s3">\legato</span>, ~legato</p>
70 <p class="p5">).play;</p>
71 <p class="p5">)</p>
72 <p class="p8"><br></p>
73 <p class="p5">p.stop;</p>
74 <p class="p5">c.remove;</p>
75 <p class="p2"><br></p>
76 <p class="p2"><br></p>
77 <p class="p3"><b>Triggering patterns by external control</b></p>
78 <p class="p2"><br></p>
79 <p class="p4">Issuing 'play' to a pattern can occur in an action function for many different kinds of objects: GUI, MIDI, OSCresponder, HID actions. This allows triggering patterns from a variety of interfaces.</p>
80 <p class="p2"><br></p>
81 <p class="p4">It's very unlikely that an action function would be triggered exactly in sync with a clock. If the pattern being played needs to run in time with other patterns, use the 'quant' argument to control its starting time (see <a href="../../Scheduling/Quant.html"><span class="s1">Quant</span></a>).</p>
82 <p class="p2"><br></p>
83 <p class="p4"><b>Triggering a pattern by a GUI</b></p>
84 <p class="p2"><br></p>
85 <p class="p5">(</p>
86 <p class="p5"><span class="s1">var</span><span class="Apple-tab-span"> </span>pattern = <span class="s1">Pbind</span>(</p>
87 <p class="p5"><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span><span class="s3">\degree</span>, <span class="s1">Pseries</span>(7, <span class="s1">Pwhite</span>(1, 3, <span class="s1">inf</span>) * <span class="s1">Prand</span>(#[-1, 1], <span class="s1">inf</span>), <span class="s1">inf</span>).fold(0, 14)</p>
88 <p class="p5"><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span>+ <span class="s1">Prand</span>(#[[0, -2, -4], [0, -3, -5], [0, -2, -5], [0, -1, -4]], <span class="s1">inf</span>),</p>
89 <p class="p5"><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span><span class="s3">\dur</span>, <span class="s1">Pwrand</span>(#[1, 0.5], #[0.8, 0.2], <span class="s1">inf</span>)</p>
90 <p class="p5"><span class="Apple-tab-span"> </span>),</p>
91 <p class="p5"><span class="Apple-tab-span"> </span>player, window;</p>
92 <p class="p8"><br></p>
93 <p class="p5">window = <span class="s1">Window</span>.new(<span class="s4">"Pattern trigger"</span>, <span class="s1">Rect</span>(5, 100, 150, 100))</p>
94 <p class="p7"><span class="s2"><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span></span>// onClose is fairly important</p>
95 <p class="p7"><span class="s2"><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span></span>// without it, closing the window could leave the pattern playing</p>
96 <p class="p5"><span class="Apple-tab-span"> </span>.onClose_({ player.stop });</p>
97 <p class="p5"><span class="s1">Button</span>.new(window, <span class="s1">Rect</span>(5, 5, 140, 90))</p>
98 <p class="p9"><span class="s2"><span class="Apple-tab-span"> </span>.states_([[</span>"Pattern GO"<span class="s2">], [</span>"Pattern STOP"<span class="s2">]])</span></p>
99 <p class="p5"><span class="Apple-tab-span"> </span>.font_(<span class="s1">Font</span>.new(<span class="s4">"Helvetica"</span>, 18))</p>
100 <p class="p5"><span class="Apple-tab-span"> </span>.action_({ <span class="s1">|button|</span></p>
101 <p class="p5"><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span>if(button.value == 1 and: { player.isNil or: { player.isPlaying.not } }) {</p>
102 <p class="p5"><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span>player = pattern.play;</p>
103 <p class="p5"><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span>} {</p>
104 <p class="p5"><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span>player.stop;</p>
105 <p class="p5"><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span>button.value = 0;</p>
106 <p class="p5"><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span>};</p>
107 <p class="p5"><span class="Apple-tab-span"> </span>});</p>
108 <p class="p5">window.front;</p>
109 <p class="p5">)</p>
110 <p class="p8"><br></p>
111 <p class="p5">p.stop;</p>
112 <p class="p2"><br></p>
113 <p class="p2"><br></p>
114 <p class="p4"><b>Triggering a pattern by MIDI</b></p>
115 <p class="p2"><br></p>
116 <p class="p5">(</p>
117 <p class="p5"><span class="s1">var</span><span class="Apple-tab-span"> </span>pattern = <span class="s1">Pbind</span>(</p>
118 <p class="p5"><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span><span class="s3">\degree</span>, <span class="s1">Pseries</span>(7, <span class="s1">Pwhite</span>(1, 3, <span class="s1">inf</span>) * <span class="s1">Prand</span>(#[-1, 1], <span class="s1">inf</span>), <span class="s1">inf</span>).fold(0, 14)</p>
119 <p class="p5"><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span>+ <span class="s1">Prand</span>(#[[0, -2, -4], [0, -3, -5], [0, -2, -5], [0, -1, -4]], <span class="s1">inf</span>),</p>
120 <p class="p5"><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span><span class="s3">\dur</span>, <span class="s1">Pwrand</span>(#[1, 0.5], #[0.8, 0.2], <span class="s1">inf</span>)</p>
121 <p class="p5"><span class="Apple-tab-span"> </span>),</p>
122 <p class="p5"><span class="Apple-tab-span"> </span>player;</p>
123 <p class="p8"><br></p>
124 <p class="p5">~noteOnResp = <span class="s1">NoteOnResponder</span>({</p>
125 <p class="p5"><span class="Apple-tab-span"> </span>if(player.isNil or: { player.isPlaying.not }) {</p>
126 <p class="p5"><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span>player = pattern.play;</p>
127 <p class="p5"><span class="Apple-tab-span"> </span>} {</p>
128 <p class="p5"><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span>player.stop;</p>
129 <p class="p5"><span class="Apple-tab-span"> </span>};</p>
130 <p class="p7">// num: 60 limits this responder to listen to middle-C only</p>
131 <p class="p7">// but it will pick up that note from any port, any channel</p>
132 <p class="p5">}, num: 60);</p>
133 <p class="p5">)</p>
134 <p class="p8"><br></p>
135 <p class="p7">// when done</p>
136 <p class="p5">~noteOnResp.remove;</p>
137 <p class="p2"><br></p>
138 <p class="p2"><br></p>
139 <p class="p3"><b>Triggering a pattern by signal amplitude</b></p>
140 <p class="p2"><br></p>
141 <p class="p4">Triggering a pattern based on audio amplitude is a bit trickier -- not because it's harder to play the pattern, but because identifying when the trigger should happen is more involved. The most straightforward way in SuperCollider is to use the <a href="../../UGens/Analysis/Amplitude.html"><span class="s1">Amplitude</span></a> UGen to get the volume of the input signal and compare it to a threshold. Volume can fluctuate rapidly, so the 'releaseTime' argument of Amplitude is set to a high value. This makes the measured amplitude fall more slowly toward the baseline, preventing triggers from being sent too close together.</p>
142 <p class="p2"><br></p>
143 <p class="p4">The actual threshold depends on the incoming signal, so the example pops up a quick and dirty window to see the measured amplitude and set the threshold and decay accordingly. The synth listens by default to the first hardware input bus, but you can change it the following in the code to use a different input bus:</p>
144 <p class="p2"><br></p>
145 <p class="p4"><span class="Apple-tab-span"> </span>inbus: s.options.numOutputBusChannels</p>
146 <p class="p2"><br></p>
147 <p class="p4">In this configuration, the first trigger starts the pattern and the second trigger stops it. You might want the pattern to play while the input signal is above the threshold, and stop when the signal drops to a quieter level. The comparison amp &gt;= thresh can send a trigger only when the signal goes from softer to lower, so if we want the pattern to stop when the signal becomes quiet, we need to send a trigger when crossing the threshold in both directions.<span class="Apple-converted-space"> </span></p>
148 <p class="p2"><br></p>
149 <p class="p5"><span class="Apple-tab-span"> </span><span class="s1">var</span><span class="Apple-tab-span"> </span>amp = <span class="s1">Amplitude</span>.kr(<span class="s1">In</span>.ar(inbus, 1), attackTime: 0.01, releaseTime: decay),</p>
150 <p class="p5"><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span>trig = <span class="s1">HPZ1</span>.kr(amp &gt;= thresh);</p>
151 <p class="p5"><span class="Apple-tab-span"> </span><span class="s1">SendTrig</span>.kr(trig.abs, 1, trig);</p>
152 <p class="p2"><br></p>
153 <p class="p4"><span class="s5"><a href="../../UGens/Filters/HPZ1.html">HPZ1</a></span> is positive if its input rises and negative if it falls. Triggering based on the absolute value, then, sends the trigger on any change. The client responding to the trigger might need to know the direction of change, so we send HPZ1's value back and the client can decide which action to take based on the sign of this value.</p>
154 <p class="p2"><br></p>
155 <p class="p4">For this example, triggers are measured only when the signal rises above the threshold.</p>
156 <p class="p2"><br></p>
157 <p class="p5">(</p>
158 <p class="p5"><span class="s1">var</span><span class="Apple-tab-span"> </span>pattern = <span class="s1">Pbind</span>(</p>
159 <p class="p5"><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span><span class="s3">\degree</span>, <span class="s1">Pseries</span>(7, <span class="s1">Pwhite</span>(1, 3, <span class="s1">inf</span>) * <span class="s1">Prand</span>(#[-1, 1], <span class="s1">inf</span>), <span class="s1">inf</span>).fold(0, 14)</p>
160 <p class="p5"><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span>+ <span class="s1">Prand</span>(#[[0, -2, -4], [0, -3, -5], [0, -2, -5], [0, -1, -4]], <span class="s1">inf</span>),</p>
161 <p class="p5"><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span><span class="s3">\dur</span>, <span class="s1">Pwrand</span>(#[1, 0.5], #[0.8, 0.2], <span class="s1">inf</span>)</p>
162 <p class="p5"><span class="Apple-tab-span"> </span>),</p>
163 <p class="p5"><span class="Apple-tab-span"> </span>player;</p>
164 <p class="p8"><br></p>
165 <p class="p7">// Quicky GUI to tune threshold and decay times</p>
166 <p class="p5">~w = <span class="s1">Window</span>(<span class="s4">"threshold setting"</span>, <span class="s1">Rect</span>(15, 100, 300, 100))</p>
167 <p class="p5"><span class="Apple-tab-span"> </span>.onClose_({</p>
168 <p class="p5"><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span>~ampSynth.free;</p>
169 <p class="p5"><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span>~ampUpdater.remove;</p>
170 <p class="p5"><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span>~oscTrigResp.remove;</p>
171 <p class="p5"><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span>player.stop;</p>
172 <p class="p5"><span class="Apple-tab-span"> </span>});</p>
173 <p class="p5">~w.view.decorator = <span class="s1">FlowLayout</span>(~w.view.bounds, 2@2, 2@2);</p>
174 <p class="p5">~ampView = <span class="s1">EZSlider</span>(~w, 295@20, <span class="s4">"amplitude"</span>, <span class="s3">\amp</span>, labelWidth: 80, numberWidth: 60);</p>
175 <p class="p5">~ampView.sliderView.canFocus_(<span class="s1">false</span>).enabled_(<span class="s1">false</span>);</p>
176 <p class="p5">~ampView.numberView.canFocus_(<span class="s1">false</span>).enabled_(<span class="s1">false</span>);</p>
177 <p class="p5"><span class="s1">StaticText</span>(~w, 295@5).background_(<span class="s1">Color</span>.gray);</p>
178 <p class="p5">~threshView = <span class="s1">EZSlider</span>(~w, 295@30, <span class="s4">"threshold"</span>, <span class="s3">\amp</span>, action: { <span class="s1">|ez|</span></p>
179 <p class="p5"><span class="Apple-tab-span"> </span>~ampSynth.set(<span class="s3">\thresh</span>, ez.value);</p>
180 <p class="p5">}, initVal: 0.4, labelWidth: 80, numberWidth: 60);</p>
181 <p class="p5">~decayView = <span class="s1">EZSlider</span>(~w, 295@30, <span class="s4">"decay"</span>, #[0.1, 100, <span class="s3">\exp</span>], action: { <span class="s1">|ez|</span></p>
182 <p class="p5"><span class="Apple-tab-span"> </span>~ampSynth.set(<span class="s3">\decay</span>, ez.value);</p>
183 <p class="p5">}, initVal: 80.0, labelWidth: 80, numberWidth: 60);</p>
184 <p class="p8"><br></p>
185 <p class="p5">~w.front;</p>
186 <p class="p8"><br></p>
187 <p class="p6"><span class="s2">~ampSynth = </span>SynthDef<span class="s2">(</span><span class="s3">\ampSynth</span><span class="s2">, { </span>|inbus, thresh = 0.8, decay = 1|</p>
188 <p class="p5"><span class="Apple-tab-span"> </span><span class="s1">var</span><span class="Apple-tab-span"> </span>amp = <span class="s1">Amplitude</span>.kr(<span class="s1">In</span>.ar(inbus, 1), attackTime: 0.01, releaseTime: decay);</p>
189 <p class="p7"><span class="s2"><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span></span>// this trigger (id==0) is to update the gui only</p>
190 <p class="p5"><span class="Apple-tab-span"> </span><span class="s1">SendTrig</span>.kr(<span class="s1">Impulse</span>.kr(10), 0, amp);</p>
191 <p class="p7"><span class="s2"><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span></span>// this trigger gets sent only when amplitude crosses threshold</p>
192 <p class="p5"><span class="Apple-tab-span"> </span><span class="s1">SendTrig</span>.kr(amp &gt;= thresh, 1, 1);</p>
193 <p class="p5">}).play(args: [inbus: s.options.numOutputBusChannels, thresh: ~threshView.value, decay: ~decayView.value]);</p>
194 <p class="p8"><br></p>
195 <p class="p5">~ampUpdater = <span class="s1">OSCpathResponder</span>(s.addr, [<span class="s3">'/tr'</span>, ~ampSynth.nodeID, 0], { <span class="s1">|time, resp, msg|</span></p>
196 <p class="p5"><span class="Apple-tab-span"> </span>defer { ~ampView.value = msg[3] }</p>
197 <p class="p5">}).add;</p>
198 <p class="p8"><br></p>
199 <p class="p5">~oscTrigResp = <span class="s1">OSCpathResponder</span>(s.addr, [<span class="s3">'/tr'</span>, ~ampSynth.nodeID, 1], { <span class="s1">|time, resp, msg|</span></p>
200 <p class="p5"><span class="Apple-tab-span"> </span>if(player.isNil or: { player.isPlaying.not }) {</p>
201 <p class="p5"><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span>player = pattern.play;</p>
202 <p class="p5"><span class="Apple-tab-span"> </span>} {</p>
203 <p class="p5"><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span>player.stop;</p>
204 <p class="p5"><span class="Apple-tab-span"> </span>};</p>
205 <p class="p5">}).add;</p>
206 <p class="p5">)</p>
207 <p class="p2"><br></p>
208 <p class="p2"><br></p>
209 <p class="p10"><span class="s2">Previous:<span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span><a href="PG_Cookbook02_Manipulating_Patterns.html"><span class="s6">PG_Cookbook02_Manipulating_Patterns</span></a></span></p>
210 <p class="p10"><span class="s2">Next:<span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span><a href="PG_Cookbook04_Sending_MIDI.html"><span class="s6">PG_Cookbook04_Sending_MIDI</span></a></span></p>
211 </body>
212 </html>